/*
 * JFFS2 -- Journalling Flash File System, Version 2.
 *
 * Copyright (C) 2001-2003 Red Hat, Inc.
 *
 * Created by David Woodhouse <dwmw2@infradead.org>
 *
 * For licensing information, see the file 'LICENCE' in this directory.
 *
 * $Id: compr_zlib.c,v 1.31 2005/05/20 19:30:06 gleixner Exp $
 *
 */
#include "jffs2_config.h"

#include <jffs2_kernel.h>
#include <jffs2_slab.h>
#include <jffs2_zlib.h>
#include "jffs2_nodelist.h"
#include "jffs2_compr.h"

/* Plan: call deflate() with avail_in == *sourcelen,
	avail_out = *dstlen - 12 and flush == Z_FINISH.
	If it doesn't manage to finish,	call it again with
	avail_in == 0 and avail_out set to the remaining 12
	bytes for it to clean up.
   Q: Is 12 bytes sufficient?
*/
#define STREAM_END_SPACE 12

static DECLARE_MUTEX(deflate_sem);
static DECLARE_MUTEX(inflate_sem);
static z_stream inf_strm, def_strm;

#ifdef __KERNEL__ /* Linux-only */
#include <linux/vmalloc.h>
#include <linux/init.h>

static int __init alloc_workspaces(void)
{
    def_strm.workspace = vmalloc(zlib_deflate_workspacesize());
    if (!def_strm.workspace)
    {
        printk(KERN_WARNING "Failed to allocate %d bytes for deflate workspace\n", zlib_deflate_workspacesize());
        return -ENOMEM;
    }
    D1(printk(KERN_DEBUG "Allocated %d bytes for deflate workspace\n", zlib_deflate_workspacesize()));
    inf_strm.workspace = vmalloc(zlib_inflate_workspacesize());
    if (!inf_strm.workspace)
    {
        printk(KERN_WARNING "Failed to allocate %d bytes for inflate workspace\n", zlib_inflate_workspacesize());
        vfree(def_strm.workspace);
        return -ENOMEM;
    }
    D1(printk(KERN_DEBUG "Allocated %d bytes for inflate workspace\n", zlib_inflate_workspacesize()));
    return 0;
}

static void free_workspaces(void)
{
    vfree(def_strm.workspace);
    vfree(inf_strm.workspace);
}
#else
#define alloc_workspaces() (0)
#define free_workspaces() do { } while(0)
#endif /* __KERNEL__ */

static int jffs2_zlib_compress(unsigned char *data_in,
                               unsigned char *cpage_out,
                               uint32_t *sourcelen, uint32_t *dstlen,
                               void *model)
{
    int ret;

    if (*dstlen <= STREAM_END_SPACE)
        return -1;

    down(&deflate_sem);

    if (Z_OK != zlib_deflateInit(&def_strm, 3))
    {
        printk(KERN_WARNING "deflateInit failed\n");
        up(&deflate_sem);
        return -1;
    }

    def_strm.next_in = data_in;
    def_strm.total_in = 0;

    def_strm.next_out = cpage_out;
    def_strm.total_out = 0;

    while (def_strm.total_out < *dstlen - STREAM_END_SPACE && def_strm.total_in < *sourcelen)
    {
        def_strm.avail_out = *dstlen - (def_strm.total_out + STREAM_END_SPACE);
        def_strm.avail_in = min((unsigned)(*sourcelen - def_strm.total_in), def_strm.avail_out);
        D1(printk(KERN_DEBUG "calling deflate with avail_in %d, avail_out %d\n",
                  def_strm.avail_in, def_strm.avail_out));
        ret = zlib_deflate(&def_strm, Z_PARTIAL_FLUSH);
        D1(printk(KERN_DEBUG "deflate returned with avail_in %d, avail_out %d, total_in %ld, total_out %ld\n",
                  def_strm.avail_in, def_strm.avail_out, def_strm.total_in, def_strm.total_out));
        if (ret != Z_OK)
        {
            D1(printk(KERN_DEBUG "deflate in loop returned %d\n", ret));
            zlib_deflateEnd(&def_strm);
            up(&deflate_sem);
            return -1;
        }
    }
    def_strm.avail_out += STREAM_END_SPACE;
    def_strm.avail_in = 0;
    ret = zlib_deflate(&def_strm, Z_FINISH);
    zlib_deflateEnd(&def_strm);

    if (ret != Z_STREAM_END)
    {
        D1(printk(KERN_DEBUG "final deflate returned %d\n", ret));
        ret = -1;
        goto out;
    }

    if (def_strm.total_out >= def_strm.total_in)
    {
        D1(printk(KERN_DEBUG "zlib compressed %ld bytes into %ld; failing\n",
                  def_strm.total_in, def_strm.total_out));
        ret = -1;
        goto out;
    }

    D1(printk(KERN_DEBUG "zlib compressed %ld bytes into %ld\n",
              def_strm.total_in, def_strm.total_out));

    *dstlen = def_strm.total_out;
    *sourcelen = def_strm.total_in;
    ret = 0;
out:
    up(&deflate_sem);
    return ret;
}

static int jffs2_zlib_decompress(unsigned char *data_in,
                                 unsigned char *cpage_out,
                                 uint32_t srclen, uint32_t destlen,
                                 void *model)
{
    int ret;
    int wbits = MAX_WBITS;

    down(&inflate_sem);

    inf_strm.next_in = data_in;
    inf_strm.avail_in = srclen;
    inf_strm.total_in = 0;

    inf_strm.next_out = cpage_out;
    inf_strm.avail_out = destlen;
    inf_strm.total_out = 0;

    /* If it's deflate, and it's got no preset dictionary, then
       we can tell zlib to skip the adler32 check. */
    if (srclen > 2 && !(data_in[1] & PRESET_DICT) &&
            ((data_in[0] & 0x0f) == Z_DEFLATED) &&
            !(((data_in[0] << 8) + data_in[1]) % 31))
    {

        D2(printk(KERN_DEBUG "inflate skipping adler32\n"));
        wbits = -((data_in[0] >> 4) + 8);
        inf_strm.next_in += 2;
        inf_strm.avail_in -= 2;
    }
    else
    {
        /* Let this remain D1 for now -- it should never happen */
        D1(printk(KERN_DEBUG "inflate not skipping adler32\n"));
    }


    if (Z_OK != zlib_inflateInit2(&inf_strm, wbits))
    {
        printk(KERN_WARNING "inflateInit failed\n");
        up(&inflate_sem);
        return 1;
    }

    while((ret = zlib_inflate(&inf_strm, Z_FINISH)) == Z_OK)
        ;
    if (ret != Z_STREAM_END)
    {
        printk(KERN_NOTICE "inflate returned %d\n", ret);
    }
    zlib_inflateEnd(&inf_strm);
    up(&inflate_sem);
    return 0;
}

static struct jffs2_compressor jffs2_zlib_comp =
{
    .priority = JFFS2_ZLIB_PRIORITY,
    .name = "zlib",
    .compr = JFFS2_COMPR_ZLIB,
    .compress = &jffs2_zlib_compress,
    .decompress = &jffs2_zlib_decompress,
#ifdef JFFS2_ZLIB_DISABLED
    .disabled = 1,
#else
    .disabled = 0,
#endif
};

int __init jffs2_zlib_init(void)
{
    int ret;

    ret = INIT_MUTEX(&deflate_sem);
    if(ret){
        return ret;
    }
    ret = INIT_MUTEX(&inflate_sem);
    if(ret){
        goto err_destroy_deflate;
    }
    ret = alloc_workspaces();
    if (ret){
        goto err_destroy_sem;
    }

    ret = jffs2_register_compressor(&jffs2_zlib_comp);
    if (ret){
        free_workspaces();
    }else{
        return ret;
    }
err_destroy_sem:
    DESTROY_MUTEX(&inflate_sem);
err_destroy_deflate:
    DESTROY_MUTEX(&deflate_sem);
    return ret;
}

void jffs2_zlib_exit(void)
{
    jffs2_unregister_compressor(&jffs2_zlib_comp);
    free_workspaces();
    DESTROY_MUTEX(&deflate_sem);
    DESTROY_MUTEX(&inflate_sem);
}
